/*
 * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
 * Copyright (c) 1991-1994 by Xerox Corporation.  All rights reserved.
 *
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
 * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 * Permission is hereby granted to use or copy this program
 * for any purpose, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 */

#ifndef GC_HEADERS_H
#define GC_HEADERS_H

#if !defined(GC_PRIVATE_H) && !defined(CPPCHECK)
#  error gc_hdrs.h should be included from gc_priv.h
#endif

#if CPP_WORDSZ != 32 && CPP_WORDSZ < 36 && !defined(CPPCHECK)
#  error Get a real machine
#endif

EXTERN_C_BEGIN

/*
 * The 2-level tree data structure that is used to find block headers.
 * If there are more than 32 bits in a pointer, the top level is a hash
 * table.
 *
 * This defines `HDR()`, `GET_HDR()`, and `SET_HDR()`, the main macros
 * used to retrieve and set object headers.
 *
 * We take advantage of a header lookup cache.  This is a locally declared
 * direct mapped cache, used inside the marker.  The `HC_GET_HDR()` macro
 * uses and maintains this cache.  Assuming we get reasonable hit rates,
 * this saves a few memory references from each pointer validation.
 */

#if CPP_WORDSZ > 32
#  define HASH_TL
#endif

/* Define appropriate out-degrees for each of the two tree levels. */
#if defined(LARGE_CONFIG) || !defined(SMALL_CONFIG)
#  define LOG_BOTTOM_SZ 10
#else
/* Keep top index size reasonable with smaller blocks. */
#  define LOG_BOTTOM_SZ 11
#endif
#define BOTTOM_SZ (1 << LOG_BOTTOM_SZ)

#ifndef HASH_TL
#  define LOG_TOP_SZ (CPP_WORDSZ - LOG_BOTTOM_SZ - LOG_HBLKSIZE)
#else
#  define LOG_TOP_SZ 11
#endif
#define TOP_SZ (1 << LOG_TOP_SZ)

#ifdef COUNT_HDR_CACHE_HITS
extern word GC_hdr_cache_hits; /*< used for debugging/profiling */
extern word GC_hdr_cache_misses;
#  define HC_HIT() (void)(++GC_hdr_cache_hits)
#  define HC_MISS() (void)(++GC_hdr_cache_misses)
#else
#  define HC_HIT() (void)0
#  define HC_MISS() (void)0
#endif

typedef struct hce {
  word block_addr; /*< right-shifted by `LOG_HBLKSIZE` */
  hdr *hce_hdr;
} hdr_cache_entry;

#define HDR_CACHE_SIZE 8 /*< a power of two */

#define DECLARE_HDR_CACHE hdr_cache_entry hdr_cache[HDR_CACHE_SIZE]

#define INIT_HDR_CACHE BZERO(hdr_cache, sizeof(hdr_cache))

#define HCE(h) (hdr_cache + ((ADDR(h) >> LOG_HBLKSIZE) & (HDR_CACHE_SIZE - 1)))

#define HCE_VALID_FOR(hce, h) ((hce)->block_addr == (ADDR(h) >> LOG_HBLKSIZE))

#define HCE_HDR(h) ((hce)->hce_hdr)

#ifdef PRINT_BLACK_LIST
/*
 * Handle a header cache miss.  Returns a pointer to the header
 * corresponding to `p`, if the latter can possibly be a valid object
 * pointer, and `NULL` otherwise.  Guaranteed to return `NULL` for
 * a pointer past the first page of an object unless both
 * `GC_all_interior_pointers` is set and `p` is in fact a valid object
 * pointer.  Never returns a pointer to a free `hblk`.
 */
GC_INNER hdr *GC_header_cache_miss(ptr_t p, hdr_cache_entry *hce,
                                   ptr_t source);

#  define HEADER_CACHE_MISS(p, hce, source) \
    GC_header_cache_miss(p, hce, source)
#else
GC_INNER hdr *GC_header_cache_miss(ptr_t p, hdr_cache_entry *hce);
#  define HEADER_CACHE_MISS(p, hce, source) GC_header_cache_miss(p, hce)
#endif

/*
 * Set `hhdr` to the header for `p`.  Analogous to `GET_HDR()` below,
 * except that in the case of large objects, it gets the header for the
 * object beginning if `GC_all_interior_pointers` is true.  Sets `hhdr`
 * to `NULL` if `p` points to somewhere other than the first page of an
 * object, and it is not a valid pointer to the object.
 */
#define HC_GET_HDR(p, hhdr, source)                \
  { /*< cannot use `do ... while (0)` here */      \
    hdr_cache_entry *hce = HCE(p);                 \
    if (LIKELY(HCE_VALID_FOR(hce, p))) {           \
      HC_HIT();                                    \
      hhdr = hce->hce_hdr;                         \
    } else {                                       \
      hhdr = HEADER_CACHE_MISS(p, hce, source);    \
      if (NULL == hhdr)                            \
        break; /*< go to the enclosing loop end */ \
    }                                              \
  }

typedef struct bi {
  /*
   * The bottom-level index contains one of three kinds of values:
   *   - 0 means we are not responsible for this block, or this is
   *     a block other than the first one in a free block;
   *   - 1 < (long)`x` <= `MAX_JUMP` means the block starts at least
   *     `x * HBLKSIZE` bytes before the current address;
   *   - a valid pointer points to a `hdr` structure (the above cannot
   *     be valid pointers due to the `GET_MEM()` return convention).
   */
  hdr *index[BOTTOM_SZ];

  /*
   * All indices are linked in the ascending and descending orders,
   * respectively.
   */
  struct bi *asc_link;
  struct bi *desc_link;

  word key; /*< high-order address bits */
#ifdef HASH_TL
  struct bi *hash_link; /*< hash chain link */
#endif
} bottom_index;

#define MAX_JUMP (HBLKSIZE - 1)

#define HDR_FROM_BI(bi, p) \
  (bi)->index[(ADDR(p) >> LOG_HBLKSIZE) & (BOTTOM_SZ - 1)]
#ifndef HASH_TL
#  define BI(p) GC_top_index[ADDR(p) >> (LOG_BOTTOM_SZ + LOG_HBLKSIZE)]
#  define HDR_INNER(p) HDR_FROM_BI(BI(p), p)
#  ifdef SMALL_CONFIG
#    define HDR(p) GC_find_header(p)
#  else
#    define HDR(p) HDR_INNER(p)
#  endif
#  define GET_BI(p, bottom_indx) (void)((bottom_indx) = BI(p))
#  define GET_HDR(p, hhdr) (void)((hhdr) = HDR(p))
#  define SET_HDR(p, hhdr) (void)(HDR_INNER(p) = (hhdr))
#  define GET_HDR_ADDR(p, ha) (void)((ha) = &HDR_INNER(p))
#else
/* A hash function for the tree top level. */
#  define TL_HASH(hi) ((hi) & (TOP_SZ - 1))
/* Set `bottom_indx` to point to the bottom index for address `p`. */
#  define GET_BI(p, bottom_indx)                                    \
    do {                                                            \
      REGISTER word hi = ADDR(p) >> (LOG_BOTTOM_SZ + LOG_HBLKSIZE); \
      REGISTER bottom_index *_bi = GC_top_index[TL_HASH(hi)];       \
      while (_bi->key != hi && _bi != GC_all_nils)                  \
        _bi = _bi->hash_link;                                       \
      (bottom_indx) = _bi;                                          \
    } while (0)
#  define GET_HDR_ADDR(p, ha)     \
    do {                          \
      REGISTER bottom_index *bi;  \
      GET_BI(p, bi);              \
      (ha) = &HDR_FROM_BI(bi, p); \
    } while (0)
#  define GET_HDR(p, hhdr)  \
    do {                    \
      REGISTER hdr **_ha;   \
      GET_HDR_ADDR(p, _ha); \
      (hhdr) = *_ha;        \
    } while (0)
#  define SET_HDR(p, hhdr)          \
    do {                            \
      REGISTER bottom_index *bi;    \
      GET_BI(p, bi);                \
      GC_ASSERT(bi != GC_all_nils); \
      HDR_FROM_BI(bi, p) = (hhdr);  \
    } while (0)
#  define HDR(p) GC_find_header(p)
#endif

/*
 * Is the result a forwarding address to someplace closer to the
 * beginning of the block or `NULL`?
 */
#define IS_FORWARDING_ADDR_OR_NIL(hhdr) ((size_t)ADDR(hhdr) <= MAX_JUMP)

/*
 * Get an `HBLKSIZE`-aligned address closer to the beginning of the
 * block `h`.  Assumes that `hhdr` is equal to `HDR(h)`,
 * `IS_FORWARDING_ADDR(hhdr)` is true and `hhdr` is not `NULL`.
 * `HDR(result)` is expected to be non-`NULL`.
 */
#define FORWARDED_ADDR(h, hhdr) \
  ((struct hblk *)(h) - (size_t)(GC_uintptr_t)(hhdr))

EXTERN_C_END

#endif /* GC_HEADERS_H */
